﻿using MS_Entity;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ModbusTCPDemo
{
    public partial class FrmMain : Form
    {
        public FrmMain()
        {
            InitializeComponent();
            this.Load += FrmMain_Load;
        }

        private void FrmMain_Load(object sender, EventArgs e)
        {
            this.cmb_Function.Items.Add("01 Coil Status(0x) 读线圈");
            this.cmb_Function.Items.Add("02 Input Status(1x) 写线圈");
            this.cmb_Function.Items.Add("03 Holding Registers(4x) 读寄存器");
            this.cmb_Function.Items.Add("04 Input Registers(3x) 写寄存器");
            this.cmb_Function.SelectedIndex = 0;

        }

        private void btn_Connect_Click(object sender, EventArgs e)
        {
            if (this.btn_Connect.Text == "Connect")
            {
                var flag = TcpConnect(this.cmb_IP.Text.Trim(), this.txt_Port.Text.Trim());
                if (flag)
                {
                    this.lab_conntectState.Text = "Connect OK ......";
                    this.lab_conntectState.ForeColor = Color.FromArgb(0, 192, 0);
                }
                else
                {
                    this.lab_conntectState.Text = "Connect Fail ......";
                    this.lab_conntectState.ForeColor = Color.Red;
                }
            }
        }
        Socket tcpClient;
        private bool TcpConnect(string ip, string port)
        {
            tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                tcpClient.Connect(new IPEndPoint(IPAddress.Parse(ip), Convert.ToInt32(port)));
            }
            catch (SocketException)
            {
                return false;
            }
            return true;
        }

        private void btn_Read_Click(object sender, EventArgs e)
        {
            byte[] Res = null;
            int Start = Convert.ToInt32(this.txt_Address.Text.Trim());
            int Length = Convert.ToInt32(this.txt_Quantity.Text.Trim());
            switch (this.cmb_Function.SelectedIndex)
            {
                case 0:
                    Res = ReadOutputStatus(Start, Length);
                    break;
                case 1:
                    break;
                case 2:
                    Res = ReadKeepReg(Start, Length);
                    break;
                case 3:
                    break;
            }

            this.list_Data.Items.Clear();
            for (int i = 0; i < Res.Length; i++)
            {
                this.list_Data.Items.Add(Res[i]);
            }
        }

        private void btn_Write_Click(object sender, EventArgs e)
        {
            FrmOperation objFrm = new FrmOperation();
            DialogResult Res = objFrm.ShowDialog();
            if (Res == DialogResult.OK)
            {
                OperationItem objItem = objFrm.item;
                if (objItem.Type == 0)
                {
                    if (ForceOnOff(objItem.Address, objItem.Value))
                        MessageBox.Show("写入成功！");
                    else
                        MessageBox.Show("写入失败！");
                }
                if(objItem.Type == 1)
                {
                    if(PreSetKeepReg(objItem.Address, ushort.Parse( objItem.Value)))
                        MessageBox.Show("写入成功！");
                    else
                        MessageBox.Show("写入失败！");
                }
                if (objItem.Type == 2)
                {
                    if (PreSetFloatKeepReg(objItem.Address, float.Parse(objItem.Value)))
                        MessageBox.Show("写入成功！");
                    else
                        MessageBox.Show("写入失败！");
                }
            }
         
        }

        /// <summary>
        /// 读输出线圈 功能 0x01
        /// </summary>
        /// <param name="iAddress">前端传入的地址</param>
        /// <param name="iLenth">前端传入数量</param>
        /// <returns></returns>
        private byte[] ReadOutputStatus(int iAddress, int iLenth)
        {
            /*实现功能：读从地址0开始的一个位
              发送报文：
              00 F8 00 00 00 06 01 01 00 00 00 01
              接受报文：
              00 F8 00 00 00 04 01 01 01 00 */

            #region Modbus TCP 发送报文解析

            //byte[0]    byte[1]    byte[2]   byte[3]   byte[4]   byte[5]   byte[6]   byte[7]   byte[8]   byte[9]   byte[10]   byte[11]

            //byte[0]    byte[1]             : 消息号-------- - 随便指定，服务器返回的数据的前两个字和这个一样

            //byte[2]   byte[3]              ：modbus标识，强制为0即可

            //byte[4]   byte[5]               ：指示排在byte[5]后面所有字节的个数，也就是总长度 - 6

            //byte[6]：                       站号，对于TCP协议来说，某些情况不重要，可以随便指定，对于rtu及ascii来说，就需要选择设备的站号信息。

            //byte[7] ：                     功能码，这里就需要填入我们的真正的想法了

            //byte[8]  byte[9]               ：起始地址，比如我们想读取地址0的数据，就填 00 00 ，如果我们想读取地址1000的数据，怎么办，填入 03 E8 ，也就是将1000转化十六进制填进去。

            //byte[10]  byte[11]              ：指定想读取的数据长度，比如我们就想读取地址0的一个数据，这里就写 00 01，如果我们想读取地址0 - 999共计一个数据的长度，就写 03 E8。和起始地址是一样的。

            #endregion

            #region Modbus TCP 接收报文解析
            //byte[0]  byte[1] :             消息号，我们之前写发送指令的时候，是多少，这里就是多少。

            //byte[2]  byte[3]：           必须都为0，代表这是modbus 通信

            //byte[4]  byte[5]：           指示byte[5]后面的所有字节数，你数数看是不是4个？所以这里是00 04，如果后面共有100个，那么这里就是 00 64

            //byte[6]：                        站号，之前我们写了FF，那么这里也就是FF

            //byte[7]：                        功能码，我们之前写了01的功能码，这里也是01，和我们发送的指令是一致的

            //byte[8]：                        指示byte[8]后面跟随的字节数量，因为跟在byte[8]后面的就是真实的数据，我们最终想要的结果就在byte[8]后面

            //byte[9]：                        真实的数据，哈哈，这肯定就是我们真正想要的东西了，我们知道一个byte有8位，但是我们只读取了一个位数据，所有这里的有效值只是byte[9]的最低位，二进制为 0000 0000 我们看到最低位为0，所以最终我们读取的地址0的线圈为断。

            #endregion
            //1.拼接报文
            ByteArray byteArray = new ByteArray(12);
            byte[] Result = null;
            byteArray.Add(new byte[]
            {
                0,  //消息号---------随便指定
                0,  //消息号---------随便指定 
                0,  //modbus标识，强制为0即可
                0,  //modbus标识，强制为0即可
                0,
                6, //指示排在byte[5]后面所有字节的个数，也就是总长度 6
                3, //从站地址
                1  //功能码  ,这是读 0x01
            });

            //起始地址 当开始地址 < 256 这个字节就是 0
            byteArray.Add((byte)((iAddress - iAddress % 256) / 256));
            byteArray.Add((byte)(iAddress % 256));
            //指定想读取的数据长度
            byteArray.Add((byte)((iLenth - iLenth % 256) / 256));
            byteArray.Add((byte)(iLenth % 256));
            //2.发送报文

            tcpClient.Send(byteArray.array, byteArray.array.Length, SocketFlags.None);

            //3.接收报文
            byte[] data = new byte[512];
            tcpClient.Receive(data, 512, SocketFlags.None);


            //4.判断报文
            // 1个字节8个位
            // 当返回的报文如果是8个位及以下则返回 1个字节
            // 当返回的报文如果是>=9 && <=18 个位的时候则返回 2个字节
            int length = 0; //字节长度
            if (iLenth % 8 == 0) //看是否是 %8是整数
            {
                length = iLenth / 8;
            }
            else
            {
                length = iLenth / 8 + 1;//假如是9 ，iLength/8 等于 1 ， 1+1=2 则表示2个字节
            }

            //5.解析报文
            if (length == data[8])
            {
                Result = ByteMsgToRes(data, 9, length);
            }
            return Result;
        }



        /// <summary>
        /// 强制输出线圈 功能码 0x05
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="Value"></param>
        /// <returns></returns>
        private bool ForceOnOff(int iAddress, string Value)
        {
            //1.拼接报文   02 41 00 00 00 06 01 05 00 00 FF 00
            ByteArray byteArray = new ByteArray(12);
            byte[] Result = null;
            byteArray.Add(new byte[]
            {
                0,  //消息号---------随便指定
                0,  //消息号---------随便指定 
                0,  //modbus标识，强制为0即可
                0,  //modbus标识，强制为0即可
                0,
                6, //指示排在byte[5]后面所有字节的个数，也就是总长度 6
                3, //从站地址
                5  //功能码  ,这是写 0x05
            });

            //起始地址 当开始地址 < 256 这个字节就是 0
            byteArray.Add((byte)((iAddress - iAddress % 256) / 256));
            byteArray.Add((byte)(iAddress % 256));

            if (Value == "1" || Value.ToUpper() == "TRUE")
                byteArray.Add(0xFF);  // 0x : 是16进制的前缀  FF : 是16进制数，等于 10进制的255
            else
                byteArray.Add(0);

            byteArray.Add(0);
            //2.发送报文

            tcpClient.Send(byteArray.array, byteArray.array.Length, SocketFlags.None);

            //3.接收报文
            byte[] data = new byte[512];
            tcpClient.Receive(data, 512, SocketFlags.None);
            //4.解析报文
            Result = ByteMsgToRes(data, 0, 12);
            //5.判断报文
            if (Result.ToString() == byteArray.array.ToString())
                return true;

            return false;
        }

        /// <summary>
        /// 读保持型寄存器 功能码 0x03
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="iLenth"></param>
        /// <returns></returns>
        private byte[] ReadKeepReg(int iAddress, int iLenth)
        {
            //1.拼接报文
            ByteArray byteArray = new ByteArray(12);
            byte[] Result = null;
            byteArray.Add(new byte[]
            {
                0,  //消息号---------随便指定
                0,  //消息号---------随便指定 
                0,  //modbus标识，强制为0即可
                0,  //modbus标识，强制为0即可
                0,
                6, //指示排在byte[5]后面所有字节的个数，也就是总长度 6
                3, //从站地址
                3  //功能码  ,这是读 0x01
            });

            //起始地址 当开始地址 < 256 这个字节就是 0
            byteArray.Add((byte)((iAddress - iAddress % 256) / 256));
            byteArray.Add((byte)(iAddress % 256));
            //指定想读取的数据长度
            byteArray.Add((byte)((iLenth - iLenth % 256) / 256));
            byteArray.Add((byte)(iLenth % 256));
            //2.发送报文

            tcpClient.Send(byteArray.array, byteArray.array.Length, SocketFlags.None);

            //3.接收报文
            byte[] data = new byte[512];
            tcpClient.Receive(data, 512, SocketFlags.None);


            //4.判断报文
            // 1个字节8个位
            // 当返回的报文如果是8个位及以下则返回 1个字节
            // 当返回的报文如果是>=9 && <=18 个位的时候则返回 2个字节
            int length = iLenth * 2; //字节长度


            //5.解析报文
            if (length == data[8])
            {
                Result = ByteMsgToRes(data, 9, length);
            }
            return Result;
        }

        /// <summary>
        /// 写单个寄存器（整数） 功能码 0x06
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        private bool PreSetKeepReg(int iAddress, UInt16 SetValue)
        {
            //1.拼接报文
            ByteArray byteArray = new ByteArray(12);
            byte[] Result = null;
            byteArray.Add(new byte[]
            {
                0,  //消息号---------随便指定
                0,  //消息号---------随便指定 
                0,  //modbus标识，强制为0即可
                0,  //modbus标识，强制为0即可
                0,
                6, //指示排在byte[5]后面所有字节的个数，也就是总长度 6
                3, //从站地址
                6  //功能码  ,这是写 0x06
            });

            //起始地址 当开始地址 < 256 这个字节就是 0
            byteArray.Add((byte)((iAddress - iAddress % 256) / 256));
            byteArray.Add((byte)(iAddress % 256));

            byteArray.Add((byte)((SetValue - SetValue % 256) / 256));
            byteArray.Add((byte)(SetValue % 256));

            //2.发送报文

            tcpClient.Send(byteArray.array, byteArray.array.Length, SocketFlags.None);

            //3.接收报文
            byte[] data = new byte[512];
            tcpClient.Receive(data, 512, SocketFlags.None);
            //4.解析报文
            Result = ByteMsgToRes(data, 0, 12);
            //5.判断报文
            if (Result.ToString() == byteArray.array.ToString())
                return true;

            return false;
        }

        /// <summary>
        /// 写双字寄存器（小数） 功能码 0x10
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        private bool PreSetFloatKeepReg(int iAddress, float SetValue)
        {
            //0x10功能码
            //实现功能：预置双字保持型寄存器
            //发送报文：
            //00 A7 00 00 00 0B 01 10 00 00 00 02 04 40 13 33 33

            //接受报文：
            //00 A7 00 00 00 06 01 10 00 00 00 02

            //1.拼接报文 
            ByteArray byteArray = new ByteArray(12);
            byte[] Result = null;
            byteArray.Add(new byte[]
            {
                0,  //消息号---------随便指定
                0,  //消息号---------随便指定 
                0,  //modbus标识，强制为0即可
                0,  //modbus标识，强制为0即可
                0,
                11, //指示排在byte[5]后面所有字节的个数，也就是总长度 6
                3, //从站地址
                16  //功能码  ,这是写 0x10
            });

            //起始地址 当开始地址 < 256 这个字节就是 0
            byteArray.Add((byte)((iAddress - iAddress % 256) / 256));
            byteArray.Add((byte)(iAddress % 256));


            byteArray.Add(new byte[] { 0, 2, 4 });

            byte[] bSetValue = new byte[4];
            bSetValue = BitConverter.GetBytes(SetValue);
            byteArray.Add(bSetValue[1]);
            byteArray.Add(bSetValue[0]);
            byteArray.Add(bSetValue[3]);
            byteArray.Add(bSetValue[2]);

            //2.发送报文

            tcpClient.Send(byteArray.array, byteArray.array.Length, SocketFlags.None);

            //3.接收报文
            byte[] data = new byte[512];
            tcpClient.Receive(data, 512, SocketFlags.None);
            //4.解析报文
            Result = ByteMsgToRes(data, 0, 12);
            byte[] SendRes = ByteMsgToRes(byteArray.array, 0, 12);
            SendRes[6] = 6;
            //5.判断报文
            if (Result.ToString() == SendRes.ToString())
                return true;

            return false;
        }
        /// <summary>
        /// 字节数组截取
        /// </summary>
        /// <param name="MsgByte"></param>
        /// <param name="Start"></param>
        /// <param name="Length"></param>
        /// <returns></returns>
        private byte[] ByteMsgToRes(byte[] MsgByte, int Start, int Length)
        {
            byte[] Result = null;
            if (MsgByte != null && MsgByte.Length > 0)
            {
                Result = new byte[Length];
                for (int i = 0; i < Length; i++)
                {
                    Result[i] = MsgByte[i + Start];
                }
            }
            return Result;
        }
    }
}
